// std stuff
#include <iostream>
#include <string>
#include <fstream>
#include <cstring>

// boost stuff
#include <boost/asio.hpp>
#include "boost/bind.hpp"

const short multicast_port = 1234;
const std::string listen_address = "0.0.0.0";
const std::string multicast_address = "224.0.0.0";
const std::string output_file = "bigbuck.ts";

class receiver
{
public:
    receiver(boost::asio::io_service& io_service,
        const boost::asio::ip::address& listen_address,
        const boost::asio::ip::address& multicast_address,
        const std::string& filename)
        :   strand_(io_service),
			socket_(io_service),
			filestream_(filename.c_str(), std::ios::binary)
			
    {
        data_ = new char[max_length];
        memset(data_, 0, max_length);
        recvd_sum_ = 0;
        
        // Create the socket so that multiple may be bound to the same address.
        boost::asio::ip::udp::endpoint listen_endpoint(
            listen_address, multicast_port);
        socket_.open(listen_endpoint.protocol());
        socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
        // Increase the buffer size of the socket (buffer size in bytes)
        // max buffer size is 262 144 bytes according to hp docs
        socket_.set_option(
            boost::asio::ip::udp::socket::receive_buffer_size(200000));
        socket_.bind(listen_endpoint);

        // Join the multicast group.
        socket_.set_option(
            boost::asio::ip::multicast::join_group(multicast_address));

        socket_.async_receive_from(
            boost::asio::buffer(data_, max_length), 
			sender_endpoint_,
			strand_.wrap(
				boost::bind(&receiver::handle_receive_from, this,
							boost::asio::placeholders::error,
							boost::asio::placeholders::bytes_transferred)));
    }

    ~receiver()
    {
        delete [] data_;
        filestream_.close();
    }

    void handle_receive_from(const boost::system::error_code& error,
        size_t bytes_recvd)
    {
        if (!error)
        {
            if(bytes_recvd > 0 || infty_) {
                recvd_sum_ += bytes_recvd;
            
                //std::cout << "bytes received: " << bytes_recvd << std::endl;
                //std::cout << "receive" << std::endl;
                //std::cout.write(data_, bytes_recvd);
                //std::cout << std::endl;

                filestream_.write(data_, bytes_recvd);
                if(infty_)
                    filestream_.flush();
                    
                socket_.async_receive_from(
                    boost::asio::buffer(data_, max_length), 
					sender_endpoint_,
					strand_.wrap(
						boost::bind(&receiver::handle_receive_from, this,
									boost::asio::placeholders::error,
									boost::asio::placeholders::bytes_transferred)));
            } else {
                std::cout << bytes_recvd << " bytes received in last packet\n";
                std::cout << recvd_sum_ << " bytes received in total\n";
            }
        } else {
            std::cout << "error: " << error.message() << std::endl;
        }
    }

private:
	boost::asio::strand strand_;
	
	boost::asio::ip::udp::socket socket_;
    boost::asio::ip::udp::endpoint sender_endpoint_;
    enum { max_length = 1024*1024*10 };
    char * data_;
    unsigned long recvd_sum_;
    static const bool infty_ = false;

    std::ofstream filestream_;
};

int main(int argc, char* argv[])
{
    try
    {

        boost::asio::io_service io_service;

        receiver r(io_service,
            boost::asio::ip::address::from_string(listen_address),
            boost::asio::ip::address::from_string(multicast_address),
            output_file);
        
        io_service.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}
